/*
 * Created: Author: Aléx Carvalho / Date: Feb 13, 2015
 * 
 * Changed: $Author: ac280593@gmail.com $ / $Date: 2015-06-04 21:21:41 -0300 (Thu, 04 Jun 2015) $
 */
package com.cpm.view.zk.tree;

import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.NoSuchElementException;
import org.zkoss.zul.TreeNode;
import org.zkoss.zul.event.TreeDataEvent;

public class DefaultTreeNode<T> implements TreeNode<T> {

    private DefaultTreeModel<T> model;

    private DefaultTreeNode<T> parent;

    private final List children;

    private T data;

    public DefaultTreeNode(T data) {
        this.data = data;

        this.children = new TreeNodeChildrenList();
    }

    @Override
    public DefaultTreeModel<T> getModel() {
        return parent != null ? parent.getModel() : model;
    }

    @Override
    public void setModel(org.zkoss.zul.DefaultTreeModel<T> model) {
        if (model != null && parent != null) {
            throw new IllegalStateException("Only root allowed, " + this);
        }

        this.model = (DefaultTreeModel<T>) model;
    }

    @Override
    public T getData() {
        return data;
    }

    @Override
    public void setData(T data) {
        this.data = data;
    }
    
    public void update(){
        DefaultTreeModel mo = getModel();

        if (mo != null) {
            mo.fireEvent(TreeDataEvent.STRUCTURE_CHANGED, mo.getPath(this), 0, 0);
        }
    }

    @Override
    public List<TreeNode<T>> getChildren() {
        return children;
    }

    public Enumeration children() {
        List<TreeNode<T>> items = getChildren();

        if (items == null) {
            items = new ArrayList();
        }

        return Collections.enumeration(items);
    }

    @Override
    public TreeNode<T> getChildAt(int childIndex) {
        return childIndex >= 0 && childIndex < getChildCount() ? (TreeNode) children.get(childIndex) : null;
    }

    @Override
    public int getChildCount() {
        return children.size();
    }

    @Override
    public TreeNode<T> getParent() {
        return parent;
    }

    protected void setParent(DefaultTreeNode<T> parent) {
        this.parent = parent;
    }

    @Override
    public int getIndex(TreeNode<T> node) {
        return children.indexOf(node);
    }

    @Override
    public boolean isLeaf() {
        return getChildCount() == 0;
    }

    @Override
    public void insert(TreeNode<T> child, int index) {
        children.add(index, child);
    }

    @Override
    public void add(TreeNode<T> child) {
        insert(child, getChildCount());
    }

    @Override
    public void remove(int index) {
        children.remove(index);
    }

    @Override
    public void remove(TreeNode<T> child) {
        children.remove(child);
    }

    public void removeFromParent() {
        if (parent != null) {
            parent.remove(this);
        }
    }

    @Override
    public String toString() {
        return getData() != null ? getData().toString() : null;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof DefaultTreeNode) {
            return ((DefaultTreeNode<T>) obj).hashCode() == this.hashCode();
        }

        return false;
    }

    @Override
    public Object clone() {
        try {
            return super.clone();
        }

        catch (CloneNotSupportedException e) {
            throw new InternalError();
        }
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 89 * hash + (this.data != null ? this.data.hashCode() : 0);
        return hash;
    }

    public static boolean isAncestor(TreeNode p, TreeNode c) {
        do {
            if (p == c) {
                return true;
            }
        }

        while ((c = c.getParent()) != null);

        return false;
    }

    public Enumeration<DefaultTreeNode> breadthFirstEnumeration() {
        return new BreadthFirstEnumeration(this);
    }

    protected class TreeNodeChildrenList extends AbstractList {

        private final ArrayList list = new ArrayList();

        @Override
        public Object get(int index) {
            return list.get(index);
        }

        @Override
        public int size() {
            return list.size();
        }

        @Override
        public Object set(int index, Object element) {
            return list.set(index, element);
        }

        @Override
        public void add(int index, Object obj) {
            if (!(obj instanceof TreeNode)) {
                throw new IllegalArgumentException("New child must be an instance of TreeNode.");
            }

            TreeNode child = (TreeNode) obj;

            if (isAncestor(child, DefaultTreeNode.this)) {
                throw new IllegalArgumentException("New child is an ancestor");
            }

            TreeNode oldp = child.getParent();

            if (oldp != null) {
                oldp.remove(child);
            }

            list.add(index, child);

            DefaultTreeNode node = DefaultTreeNode.this;
            if (child instanceof DefaultTreeNode) {
                ((DefaultTreeNode) child).setParent(node);
            }

            DefaultTreeModel model = getModel();

            if (model != null) {
                model.fireEvent(TreeDataEvent.INTERVAL_ADDED, model.getPath(node), index, index);
            }

        }

        @Override
        public Object remove(int index) {
            Object child = list.remove(index);

            if (child instanceof DefaultTreeNode) {
                ((DefaultTreeNode) child).setParent(null);
            }

            DefaultTreeModel model = getModel();

            if (model != null) {
                model.fireEvent(TreeDataEvent.INTERVAL_REMOVED, model.getPath(DefaultTreeNode.this), index, index);
                model.removeFromSelection(child);
                model.removeOpenPath(model.getPath(child));
            }

            return child;
        }

        @Override
        public boolean remove(Object child) {
            int index = list.indexOf(child);

            if (index < 0) {
                return false;
            }

            remove(index);

            return true;
        }
    }

    private final class BreadthFirstEnumeration implements Enumeration<DefaultTreeNode> {

        protected Queue queue;

        public BreadthFirstEnumeration(TreeNode node) {
            super();
            queue = new Queue();
            queue.enqueue(node);
        }

        @Override
        public boolean hasMoreElements() {
            return (!queue.isEmpty() && ((Enumeration) queue.firstObject()).hasMoreElements());
        }

        @Override
        public DefaultTreeNode nextElement() {
            Enumeration enumer = (Enumeration) queue.firstObject();
            DefaultTreeNode node = (DefaultTreeNode) enumer.nextElement();
            Enumeration children = node.children();

            if (!enumer.hasMoreElements()) {
                queue.dequeue();
            }

            if (children.hasMoreElements()) {
                queue.enqueue(children);
            }

            return node;
        }

        final class Queue {

            QNode head;	// null if empty
            QNode tail;

            final class QNode {

                public Object object;
                public QNode next;	// null if end

                public QNode(Object object, QNode next) {
                    this.object = object;
                    this.next = next;
                }
            }

            public void enqueue(Object anObject) {
                if (head == null) {
                    head = tail = new QNode(anObject, null);
                }
                else {
                    tail.next = new QNode(anObject, null);
                    tail = tail.next;
                }
            }

            public Object dequeue() {
                if (head == null) {
                    throw new NoSuchElementException("No more elements");
                }

                Object retval = head.object;
                QNode oldHead = head;
                head = head.next;

                if (head == null) {
                    tail = null;
                }

                else {
                    oldHead.next = null;
                }

                return retval;
            }

            public Object firstObject() {
                if (head == null) {
                    throw new NoSuchElementException("No more elements");
                }

                return head.object;
            }

            public boolean isEmpty() {
                return head == null;
            }
        }
    }
}
